/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.nineoldandroids.animation;

import android.view.animation.Interpolator;

/**
 * This class holds a time/value pair for an animation. The Keyframe class is
 * used by {@link ValueAnimator} to define the values that the animation target
 * will have over the course of the animation. As the time proceeds from one
 * keyframe to the other, the value of the target object will animate between
 * the value at the previous keyframe and the value at the next keyframe. Each
 * keyframe also holds an optional {@link TimeInterpolator} object, which
 * defines the time interpolation over the intervalue preceding the keyframe.
 *
 * <p>
 * The Keyframe class itself is abstract. The type-specific factory methods will
 * return a subclass of Keyframe specific to the type of value being stored.
 * This is done to improve performance when dealing with the most common cases
 * (e.g., <code>float</code> and <code>int</code> values). Other types will fall
 * into a more general Keyframe class that treats its values as Objects. Unless
 * your animation requires dealing with a custom type or a data structure that
 * needs to be animated directly (and evaluated using an implementation of
 * {@link TypeEvaluator}), you should stick to using float and int as animations
 * using those types have lower runtime overhead than other types.
 * </p>
 */
public abstract class Keyframe implements Cloneable {
	/**
	 * The time at which mValue will hold true.
	 */
	float mFraction;

	/**
	 * The type of the value in this Keyframe. This type is determined at
	 * construction time, based on the type of the <code>value</code> object
	 * passed into the constructor.
	 */
	Class mValueType;

	/**
	 * The optional time interpolator for the interval preceding this keyframe.
	 * A null interpolator (the default) results in linear interpolation over
	 * the interval.
	 */
	private/* Time */Interpolator mInterpolator = null;

	/**
	 * Flag to indicate whether this keyframe has a valid value. This flag is
	 * used when an animation first starts, to populate placeholder keyframes
	 * with real values derived from the target object.
	 */
	boolean mHasValue = false;

	/**
	 * Constructs a Keyframe object with the given time and value. The time
	 * defines the time, as a proportion of an overall animation's duration, at
	 * which the value will hold true for the animation. The value for the
	 * animation between keyframes will be calculated as an interpolation
	 * between the values at those keyframes.
	 *
	 * @param fraction
	 *            The time, expressed as a value between 0 and 1, representing
	 *            the fraction of time elapsed of the overall animation
	 *            duration.
	 * @param value
	 *            The value that the object will animate to as the animation
	 *            time approaches the time in this keyframe, and the the value
	 *            animated from as the time passes the time in this keyframe.
	 */
	public static Keyframe ofInt(float fraction, int value) {
		return new IntKeyframe(fraction, value);
	}

	/**
	 * Constructs a Keyframe object with the given time. The value at this time
	 * will be derived from the target object when the animation first starts
	 * (note that this implies that keyframes with no initial value must be used
	 * as part of an {@link ObjectAnimator}). The time defines the time, as a
	 * proportion of an overall animation's duration, at which the value will
	 * hold true for the animation. The value for the animation between
	 * keyframes will be calculated as an interpolation between the values at
	 * those keyframes.
	 *
	 * @param fraction
	 *            The time, expressed as a value between 0 and 1, representing
	 *            the fraction of time elapsed of the overall animation
	 *            duration.
	 */
	public static Keyframe ofInt(float fraction) {
		return new IntKeyframe(fraction);
	}

	/**
	 * Constructs a Keyframe object with the given time and value. The time
	 * defines the time, as a proportion of an overall animation's duration, at
	 * which the value will hold true for the animation. The value for the
	 * animation between keyframes will be calculated as an interpolation
	 * between the values at those keyframes.
	 *
	 * @param fraction
	 *            The time, expressed as a value between 0 and 1, representing
	 *            the fraction of time elapsed of the overall animation
	 *            duration.
	 * @param value
	 *            The value that the object will animate to as the animation
	 *            time approaches the time in this keyframe, and the the value
	 *            animated from as the time passes the time in this keyframe.
	 */
	public static Keyframe ofFloat(float fraction, float value) {
		return new FloatKeyframe(fraction, value);
	}

	/**
	 * Constructs a Keyframe object with the given time. The value at this time
	 * will be derived from the target object when the animation first starts
	 * (note that this implies that keyframes with no initial value must be used
	 * as part of an {@link ObjectAnimator}). The time defines the time, as a
	 * proportion of an overall animation's duration, at which the value will
	 * hold true for the animation. The value for the animation between
	 * keyframes will be calculated as an interpolation between the values at
	 * those keyframes.
	 *
	 * @param fraction
	 *            The time, expressed as a value between 0 and 1, representing
	 *            the fraction of time elapsed of the overall animation
	 *            duration.
	 */
	public static Keyframe ofFloat(float fraction) {
		return new FloatKeyframe(fraction);
	}

	/**
	 * Constructs a Keyframe object with the given time and value. The time
	 * defines the time, as a proportion of an overall animation's duration, at
	 * which the value will hold true for the animation. The value for the
	 * animation between keyframes will be calculated as an interpolation
	 * between the values at those keyframes.
	 *
	 * @param fraction
	 *            The time, expressed as a value between 0 and 1, representing
	 *            the fraction of time elapsed of the overall animation
	 *            duration.
	 * @param value
	 *            The value that the object will animate to as the animation
	 *            time approaches the time in this keyframe, and the the value
	 *            animated from as the time passes the time in this keyframe.
	 */
	public static Keyframe ofObject(float fraction, Object value) {
		return new ObjectKeyframe(fraction, value);
	}

	/**
	 * Constructs a Keyframe object with the given time. The value at this time
	 * will be derived from the target object when the animation first starts
	 * (note that this implies that keyframes with no initial value must be used
	 * as part of an {@link ObjectAnimator}). The time defines the time, as a
	 * proportion of an overall animation's duration, at which the value will
	 * hold true for the animation. The value for the animation between
	 * keyframes will be calculated as an interpolation between the values at
	 * those keyframes.
	 *
	 * @param fraction
	 *            The time, expressed as a value between 0 and 1, representing
	 *            the fraction of time elapsed of the overall animation
	 *            duration.
	 */
	public static Keyframe ofObject(float fraction) {
		return new ObjectKeyframe(fraction, null);
	}

	/**
	 * Indicates whether this keyframe has a valid value. This method is called
	 * internally when an {@link ObjectAnimator} first starts; keyframes without
	 * values are assigned values at that time by deriving the value for the
	 * property from the target object.
	 *
	 * @return boolean Whether this object has a value assigned.
	 */
	public boolean hasValue() {
		return mHasValue;
	}

	/**
	 * Gets the value for this Keyframe.
	 *
	 * @return The value for this Keyframe.
	 */
	public abstract Object getValue();

	/**
	 * Sets the value for this Keyframe.
	 *
	 * @param value
	 *            value for this Keyframe.
	 */
	public abstract void setValue(Object value);

	/**
	 * Gets the time for this keyframe, as a fraction of the overall animation
	 * duration.
	 *
	 * @return The time associated with this keyframe, as a fraction of the
	 *         overall animation duration. This should be a value between 0 and
	 *         1.
	 */
	public float getFraction() {
		return mFraction;
	}

	/**
	 * Sets the time for this keyframe, as a fraction of the overall animation
	 * duration.
	 *
	 * @param fraction
	 *            time associated with this keyframe, as a fraction of the
	 *            overall animation duration. This should be a value between 0
	 *            and 1.
	 */
	public void setFraction(float fraction) {
		mFraction = fraction;
	}

	/**
	 * Gets the optional interpolator for this Keyframe. A value of
	 * <code>null</code> indicates that there is no interpolation, which is the
	 * same as linear interpolation.
	 *
	 * @return The optional interpolator for this Keyframe.
	 */
	public/* Time */Interpolator getInterpolator() {
		return mInterpolator;
	}

	/**
	 * Sets the optional interpolator for this Keyframe. A value of
	 * <code>null</code> indicates that there is no interpolation, which is the
	 * same as linear interpolation.
	 *
	 * @return The optional interpolator for this Keyframe.
	 */
	public void setInterpolator(/* Time */Interpolator interpolator) {
		mInterpolator = interpolator;
	}

	/**
	 * Gets the type of keyframe. This information is used by ValueAnimator to
	 * determine the type of {@link TypeEvaluator} to use when calculating
	 * values between keyframes. The type is based on the type of Keyframe
	 * created.
	 *
	 * @return The type of the value stored in the Keyframe.
	 */
	public Class getType() {
		return mValueType;
	}

	@Override
	public abstract Keyframe clone();

	/**
	 * This internal subclass is used for all types which are not int or float.
	 */
	static class ObjectKeyframe extends Keyframe {

		/**
		 * The value of the animation at the time mFraction.
		 */
		Object mValue;

		ObjectKeyframe(float fraction, Object value) {
			mFraction = fraction;
			mValue = value;
			mHasValue = (value != null);
			mValueType = mHasValue ? value.getClass() : Object.class;
		}

		public Object getValue() {
			return mValue;
		}

		public void setValue(Object value) {
			mValue = value;
			mHasValue = (value != null);
		}

		@Override
		public ObjectKeyframe clone() {
			ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), mValue);
			kfClone.setInterpolator(getInterpolator());
			return kfClone;
		}
	}

	/**
	 * Internal subclass used when the keyframe value is of type int.
	 */
	static class IntKeyframe extends Keyframe {

		/**
		 * The value of the animation at the time mFraction.
		 */
		int mValue;

		IntKeyframe(float fraction, int value) {
			mFraction = fraction;
			mValue = value;
			mValueType = int.class;
			mHasValue = true;
		}

		IntKeyframe(float fraction) {
			mFraction = fraction;
			mValueType = int.class;
		}

		public int getIntValue() {
			return mValue;
		}

		public Object getValue() {
			return mValue;
		}

		public void setValue(Object value) {
			if (value != null && value.getClass() == Integer.class) {
				mValue = ((Integer) value).intValue();
				mHasValue = true;
			}
		}

		@Override
		public IntKeyframe clone() {
			IntKeyframe kfClone = new IntKeyframe(getFraction(), mValue);
			kfClone.setInterpolator(getInterpolator());
			return kfClone;
		}
	}

	/**
	 * Internal subclass used when the keyframe value is of type float.
	 */
	static class FloatKeyframe extends Keyframe {
		/**
		 * The value of the animation at the time mFraction.
		 */
		float mValue;

		FloatKeyframe(float fraction, float value) {
			mFraction = fraction;
			mValue = value;
			mValueType = float.class;
			mHasValue = true;
		}

		FloatKeyframe(float fraction) {
			mFraction = fraction;
			mValueType = float.class;
		}

		public float getFloatValue() {
			return mValue;
		}

		public Object getValue() {
			return mValue;
		}

		public void setValue(Object value) {
			if (value != null && value.getClass() == Float.class) {
				mValue = ((Float) value).floatValue();
				mHasValue = true;
			}
		}

		@Override
		public FloatKeyframe clone() {
			FloatKeyframe kfClone = new FloatKeyframe(getFraction(), mValue);
			kfClone.setInterpolator(getInterpolator());
			return kfClone;
		}
	}
}
